Список Задач + SQLite + Interface + DI
➡️Ссылка на репозиторий с кодом этого урока
Подход 1: "Чистый" SQL
Этот подход дает полный контроль над запросом. Вы пишете SQL-команду в виде строки и передаете ее на выполнение. Для защиты от SQL-инъекций параметры передаются отдельно в виде списка.

Добавляем в проект новый файл lib/services/sqlite_service.dart
Файл lib/services/sqlite_service.dart
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import '../services/storage_interface.dart';
import '../models/task.dart';
class SqliteService implements IStorageService {
// Приватная переменная для хранения экземпляра БД
Database? _database;
static const _dbName = 'tasks_database.db'; // Название файла с бд
static const _tasksTableName = 'tasks'; // 1 таблица для списка задач
static const _settingsTableName = 'settings'; // 2 таблица для настроек
// Геттер для получения экземпляра БД.
// Если он еще не создан, инициализирует его.
Future<Database> get database async {
if (_database != null) {
return _database!;
}
_database = await _initDB();
return _database!;
}
/// Инициализация базы данных
Future<Database> _initDB() async {
// Получаем путь к каталогу для баз данных по умолчанию
// Для Android это data/data/<package_name>/databases
final dbPath = await getDatabasesPath();
// Соединяем путь и имя файла БД
final path = join(dbPath, _dbName);
return await openDatabase(
path,
version: 1,
// onCreate выполняется 1 раз
onCreate: (db, version) async {
// Выполняем SQL-запрос для создания таблицы
await db.execute('''
CREATE TABLE $_tasksTableName(
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
isDone INTEGER NOT NULL
)
''');
// Выполняем SQL-запрос для создания таблицы
await db.execute('''
CREATE TABLE $_settingsTableName(
key TEXT PRIMARY KEY,
value INTEGER
)''');
},
);
}
/// Создание новой задачи
@override
Future<Task> createTask(String text) async {
final db = await database;
// Используем INSERT для добавления новой записи в таблицу
// Метод rawInsert возвращает ID новой записи
final newId = await db.rawInsert(
'INSERT INTO $_tasksTableName(text, isDone) VALUES(?, ?)',
[text, 0],
);
// Создаём и возвращаем объект Task с ID из базы данных.
return Task(id: newId, text: text, isDone: false);
}
/// Получение всех задач
@override
Future<List<Task>> getAllTasks() async {
final db = await database;
// Используем SELECT для получения ВСЕХ записей из таблицы
final List<Map<String, dynamic>> allTasks = await db.rawQuery(
'SELECT * FROM $_tasksTableName',
);
// Преобразуем список Map в список объектов Task и возвращаем его
return List.generate(allTasks.length, (i) {
return Task(
id: allTasks[i]['id'],
text: allTasks[i]['text'],
isDone: allTasks[i]['isDone'] == 1,
);
});
}
/// Обновление задачи
@override
Future<void> updateTask(Task task) async {
final db = await database;
// Используем UPDATE для обновления записи по её ID
await db.rawUpdate(
'UPDATE $_tasksTableName SET isDone = ? WHERE id = ?',
[task.isDone ? 1 : 0, task.id],
);
}
/// Удаление задачи
@override
Future<void> deleteTask(int id) async {
final db = await database;
// Используем DELETE для удаления записи по её ID
await db.rawDelete('DELETE FROM $_tasksTableName WHERE id = ?', [id]);
}
/// Получение цветовой темы
@override
Future<bool> getThemeMode() async {
final db = await database;
final List<Map<String, dynamic>> settings = await db.rawQuery(
'SELECT value FROM $_settingsTableName WHERE key = ?',
['isDarkMode'],
);
if (settings.isNotEmpty) {
// В таблице настроек будет только одна запись, то достаём её через first
// Если будет 1 == 1 то вернётся значение true, иначе false
return settings.first['value'] == 1;
}
return false;
}
/// Сохранение цветовой темы
@override
Future<void> saveThemeMode(bool isDarkMode) async {
final db = await database;
await db.rawInsert(
'INSERT OR REPLACE INTO $_settingsTableName (key, value) VALUES (?, ?)',
['isDarkMode', isDarkMode ? 1 : 0],
);
}
}